package com.sky.utils;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

@Slf4j
@Component
public class CacheClient {

    private final StringRedisTemplate stringRedisTemplate;

    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    public CacheClient(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 设置缓存
     */
    public void set(String key, Object value, Long time, TimeUnit unit) {
        try {
            String jsonStr = JSONUtil.toJsonStr(value);
            stringRedisTemplate.opsForValue().set(key, jsonStr, time, unit);
            log.debug("缓存设置成功, key: {}, 数据长度: {}", key, jsonStr.length());
        } catch (Exception e) {
            log.error("缓存写入失败, key: {}", key, e);
        }
    }

    /**
     * 设置带逻辑过期时间的缓存
     */
    public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {
        try {
            // 设置逻辑过期时间
            RedisData redisData = new RedisData();
            redisData.setData(value);
            redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
            // 写入redis
            String jsonStr = JSONUtil.toJsonStr(redisData);
            stringRedisTemplate.opsForValue().set(key, jsonStr);
            log.debug("逻辑过期缓存设置成功, key: {}", key);
        } catch (Exception e) {
            log.error("逻辑过期缓存写入失败, key: {}", key, e);
        }
    }

    /**
     * 缓存穿透查询 - 修复版本
     */
    public <R> R queryWithPassThrough(String key, Class<R> type,
                                      Function<String, R> dbFallback,
                                      Long time, TimeUnit unit) {
        try {
            // 1. 从redis查询缓存数据
            String json = stringRedisTemplate.opsForValue().get(key);

            // 2. 判断是否存在
            if (StrUtil.isNotBlank(json)) {
                // 3. 存在，验证JSON格式并返回
                if (isValidJson(json)) {
                    log.debug("缓存命中, key: {}", key);
                    return JSONUtil.toBean(json, type);
                } else {
                    log.warn("缓存数据格式无效, 删除缓存: {}, 内容: {}", key, json);
                    stringRedisTemplate.delete(key);
                }
            }

            // 判断是否为空值（缓存穿透处理）
            if (json != null) {
                log.debug("空值命中, key: {}", key);
                return null;
            }

            // 4. 不存在，查询数据库
            log.debug("缓存未命中, 查询数据库, key: {}", key);
            R r = dbFallback.apply(key);
            if (r == null) {
                // 5. 数据库也不存在，将空值写入redis（缓存穿透处理）
                stringRedisTemplate.opsForValue().set(key, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
                log.debug("数据库无数据, 设置空值缓存, key: {}", key);
                return null;
            }

            // 6. 存在，写入redis
            this.set(key, r, time, unit);
            log.debug("数据库查询成功, 设置缓存, key: {}", key);
            return r;

        } catch (Exception e) {
            log.error("缓存查询异常, key: {}, 降级到数据库查询", key, e);
            // 降级到数据库查询
            return dbFallback.apply(key);
        }
    }

    /**
     * 带逻辑过期的缓存查询
     */
    public <R> R queryWithLogicalExpire(String key, Class<R> type,
                                        Function<String, R> dbFallback,
                                        Long time, TimeUnit unit) {
        try {
            // 1. 从redis查询缓存
            String json = stringRedisTemplate.opsForValue().get(key);

            // 2. 判断是否存在
            if (StrUtil.isBlank(json)) {
                return null;
            }

            // 3. 命中，需要先把json反序列化为对象
            RedisData redisData = JSONUtil.toBean(json, RedisData.class);
            R r = JSONUtil.toBean(JSONUtil.parseObj(redisData.getData()), type);
            LocalDateTime expireTime = redisData.getExpireTime();

            // 4. 判断是否过期
            if (expireTime.isAfter(LocalDateTime.now())) {
                // 4.1. 未过期，直接返回
                return r;
            }

            // 4.2. 已过期，需要缓存重建
            // 5. 缓存重建
            String lockKey = RedisConstants.LOCK_SHOP_KEY + key;
            boolean isLock = tryLock(lockKey);
            if (isLock) {
                // 5.1. 获取锁成功，开启独立线程实现缓存重建
                CACHE_REBUILD_EXECUTOR.submit(() -> {
                    try {
                        // 查询数据库
                        R newR = dbFallback.apply(key);
                        // 重建缓存
                        this.setWithLogicalExpire(key, newR, time, unit);
                    } catch (Exception e) {
                        log.error("缓存重建失败, key: {}", key, e);
                    } finally {
                        // 释放锁
                        unlock(lockKey);
                    }
                });
            }
            // 5.2. 返回过期的信息
            return r;

        } catch (Exception e) {
            log.error("逻辑过期缓存查询异常, key: {}", key, e);
            return null;
        }
    }

    /**
     * 带互斥锁的缓存查询
     */
    public <R> R queryWithMutex(String key, Class<R> type,
                                Function<String, R> dbFallback,
                                Long time, TimeUnit unit) {
        try {
            // 1. 从redis查询缓存
            String json = stringRedisTemplate.opsForValue().get(key);

            // 2. 判断是否存在
            if (StrUtil.isNotBlank(json)) {
                if (isValidJson(json)) {
                    return JSONUtil.toBean(json, type);
                } else {
                    log.warn("缓存数据格式无效, 删除缓存: {}", key);
                    stringRedisTemplate.delete(key);
                }
            }

            // 判断是否为空值
            if (json != null) {
                return null;
            }

            // 3. 实现缓存重建
            // 3.1. 获取互斥锁
            String lockKey = RedisConstants.LOCK_SHOP_KEY + key;
            R r = null;
            try {
                boolean isLock = tryLock(lockKey);
                // 3.2. 判断是否获取成功
                if (!isLock) {
                    // 3.3. 获取锁失败，休眠并重试
                    Thread.sleep(50);
                    return queryWithMutex(key, type, dbFallback, time, unit);
                }

                // 3.4. 获取锁成功，检查缓存是否已被其他线程重建（双重检查）
                String doubleCheckJson = stringRedisTemplate.opsForValue().get(key);
                if (StrUtil.isNotBlank(doubleCheckJson) && isValidJson(doubleCheckJson)) {
                    return JSONUtil.toBean(doubleCheckJson, type);
                }

                // 3.5. 根据key查询数据库
                r = dbFallback.apply(key);
                // 4. 不存在，返回错误
                if (r == null) {
                    // 将空值写入redis
                    stringRedisTemplate.opsForValue().set(key, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
                    return null;
                }
                // 5. 存在，写入redis
                this.set(key, r, time, unit);

            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException(e);
            } finally {
                // 6. 释放锁
                unlock(lockKey);
            }
            return r;

        } catch (Exception e) {
            log.error("互斥锁缓存查询异常, key: {}", key, e);
            return dbFallback.apply(key);
        }
    }

    /**
     * 验证JSON格式是否有效
     */
    private boolean isValidJson(String json) {
        if (StrUtil.isBlank(json)) {
            return false;
        }
        String trimmed = json.trim();
        return trimmed.startsWith("{") || trimmed.startsWith("[");
    }

    /**
     * 尝试获取锁
     */
    private boolean tryLock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

    /**
     * 释放锁
     */
    private void unlock(String key) {
        stringRedisTemplate.delete(key);
    }
}